package com.getan.boot.jdbc.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.getan.boot.jdbc.customquery.*;
import com.getan.boot.jdbc.entity.Entity;
import com.getan.boot.jdbc.entity.LogicEntity;
import com.getan.boot.jdbc.repository.GenericRepository;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 通用Service实现类
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
public class GenericService<R extends GenericRepository<E>, E extends Entity>
        implements IGenericService<E>, CustomQueryService<E> {

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected R genericRepository;
    protected Class<E> entityClass = currentEntityClass();
    protected Class<R> repositoryClass = currentRepositoryClass();

    @Override
    public boolean insertBatch(Collection<E> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    @Override
    public boolean update(E entity, Wrapper<E> updateWrapper) {
        E instance;
        if (entity == null) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
            instance = tableInfo.newInstance();
        } else {
            instance = entity;
        }
        return SqlHelper.retBool(getGenericRepository().update(instance, updateWrapper));
    }

    @Override
    public boolean updateBatch(Collection<E> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<E> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

    @Override
    public boolean delete(Serializable id) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            E instance = tableInfo.newInstance();
            tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), id);
            return SqlHelper.retBool(getGenericRepository().deleteById(instance));
        }
        return SqlHelper.retBool(getGenericRepository().deleteById(id));
    }

    @Override
    public boolean deleteBatch(Collection<? extends Serializable> ids, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.DELETE_BY_ID);
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        return executeBatch(ids, batchSize, (sqlSession, e) -> {
            if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
                E instance = tableInfo.newInstance();
                tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), e);
                sqlSession.update(sqlStatement, instance);
            } else {
                sqlSession.update(sqlStatement, e);
            }
        });
    }

    @Override
    public boolean delete(Wrapper<E> wrapper) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (LogicEntity.class.isAssignableFrom(tableInfo.getEntityType())) {
            E instance = tableInfo.newInstance();
            if (wrapper instanceof LambdaUpdateWrapper) {
                ((LambdaUpdateWrapper<E>) wrapper).setSql(LogicEntity.DELETE_SQL);
            } else if (wrapper instanceof UpdateWrapper) {
                ((UpdateWrapper<E>) wrapper).setSql(LogicEntity.DELETE_SQL);
            }
            return SqlHelper.retBool(getGenericRepository().update(instance, wrapper));
        }
        return SqlHelper.retBool(getGenericRepository().delete(wrapper));
    }

    @Override
    public E getOne(Wrapper<E> queryWrapper, boolean throwEx) {
        if (throwEx) {
            return genericRepository.selectOne(queryWrapper);
        }
        return SqlHelper.getObject(log, genericRepository.selectList(queryWrapper));
    }

    @Override
    public <V> V getObj(Wrapper<E> queryWrapper, Function<E, V> mapper) {
        return SqlHelper.getObject(log, listObjs(queryWrapper, mapper));
    }

    /**
     * 获取mapperStatementId
     *
     * @param sqlMethod 方法名
     * @return 命名id
     * @since 3.4.0
     */
    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(repositoryClass, sqlMethod);
    }

    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @return 操作结果
     * @since 3.3.1
     */
    protected <T> boolean executeBatch(Collection<T> list, int batchSize, BiConsumer<SqlSession, T> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
    }

    /**
     * 执行批量操作（默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE}）
     *
     * @param list     数据集合
     * @param consumer 执行方法
     * @return 操作结果
     * @since 3.3.1
     */
    protected <T> boolean executeBatch(Collection<T> list, BiConsumer<SqlSession, T> consumer) {
        return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
    }

    @Override
    public QueryWrapper<E> customQueryWrapper(CustomQueryBO bo) {
        QueryWrapper<E> queryWrapper = Wrappers.query();
        if (CollectionUtils.isNotEmpty(bo.getSorter())) {
            for (CustomQuerySort sorted : bo.getSorter()) {
                if (sorted.getOrder() == OrderBy.descend) {
                    queryWrapper.orderByDesc(sorted.getProperty());
                } else {
                    queryWrapper.orderByAsc(sorted.getProperty());
                }
            }
        }
        if (CollectionUtils.isNotEmpty(bo.getConditions())) {
            for (CustomQueryOption option : bo.getConditions()) {
                switch (option.getMode()) {
                    case EQ:
                        queryWrapper.eq(option.getProperty(), option.getValues()[0]);
                        break;
                    case GT:
                        queryWrapper.gt(option.getProperty(), option.getValues()[0]);
                        break;
                    case EGT:
                        queryWrapper.ge(option.getProperty(), option.getValues()[0]);
                        break;
                    case LT:
                        queryWrapper.lt(option.getProperty(), option.getValues()[0]);
                        break;
                    case ELT:
                        queryWrapper.le(option.getProperty(), option.getValues()[0]);
                        break;
                    case BETWEEN:
                        queryWrapper.between(option.getProperty(), option.getValues()[0], option.getValues()[1]);
                        break;
                    case LIKE:
                        queryWrapper.like(option.getProperty(), option.getValues()[0]);
                        break;
                    case PRE_LIKE:
                        queryWrapper.likeRight(option.getProperty(), option.getValues()[0]);
                        break;
                    case IN:
                        queryWrapper.in(option.getProperty(), option.getValues());
                        break;
                    default:
                }
            }
        }
        if (CollectionUtils.isNotEmpty(bo.getQueryFields())) {
            queryWrapper.select(bo.getQueryFields().toArray(new String[0]));
        }
        return queryWrapper;
    }

    @Override
    public R getGenericRepository() {
        return genericRepository;
    }

    @SuppressWarnings("unchecked")
    protected Class<R> currentRepositoryClass() {
        return (Class<R>) ReflectionKit.getSuperClassGenericType(this.getClass(), GenericService.class, 0);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<E> currentEntityClass() {
        return (Class<E>) ReflectionKit.getSuperClassGenericType(this.getClass(), GenericService.class, 1);
    }

}
